package com.idea.relax.redis.limiter.aspect;

import com.idea.relax.redis.limiter.annotation.RedisLimiter;
import com.idea.relax.redis.limiter.client.RedisLimiterClient;
import com.idea.relax.redis.limiter.config.RelaxRedisLimiterProperties;
import com.idea.relax.redis.limiter.reject.RedisLimiterRejectStrategy;
import com.idea.relax.redis.support.spel.EvaluationContextRootObject;
import com.idea.relax.redis.support.spel.IExpressionEvaluator;
import com.idea.relax.redis.support.utils.RelaxUtil;
import lombok.AllArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @className: RedisLimiterAspect
 * @description:
 * @author: salad
 * @date: 2023/02/14
 **/
@Aspect
@AllArgsConstructor
public class RedisLimiterAspect {

	private final RedisLimiterClient redisLimiterClient;

	private final RelaxRedisLimiterProperties properties;

	private final IExpressionEvaluator expressionEvaluator;

	private final String isolationModeKey;

	private final RedisLimiterRejectStrategy limiterRejectStrategist;


	@Around("@annotation(redisLimiter)")
	public Object aroundRedisLimiter(ProceedingJoinPoint point, RedisLimiter redisLimiter) throws Throwable {
		String key = redisLimiter.value();
		Assert.hasText(key, "请指定一个限流的标识key！");
		String param = redisLimiter.param();
		if (properties.isIsolationMode()
			&& RelaxUtil.isNotBlank(isolationModeKey)) {
			key = isolationModeKey + key;
		}
		if (RelaxUtil.isNotBlank(properties.getLimiterPrefix())) {
			key = properties.getLimiterPrefix() + key;
		}
		if (RelaxUtil.isNotBlank(param)) {
			key += RelaxUtil.COLON + resolveParam(point, param);
		}
		long count = redisLimiter.count();
		long period = redisLimiter.period();
		TimeUnit timeUnit = redisLimiter.timeUnit();
		if (!redisLimiterClient.isAllowed(key, count, period, timeUnit)) {
			return limiterRejectStrategist.reject(key, count, period, timeUnit);
		}
		return point.proceed();
	}


	private String resolveParam(ProceedingJoinPoint point, String param) {
		EvaluationContextRootObject rootObject = new EvaluationContextRootObject();
		rootObject.setClassName(Objects.requireNonNull(point.getTarget()).getClass().getSimpleName());
		MethodSignature ms = (MethodSignature) point.getSignature();
		rootObject.setMethodName(ms.getMethod().getName());
		rootObject.setTargetClass(point.getTarget().getClass());
		rootObject.setTarget(point.getTarget());
		rootObject.setArgs(point.getArgs());
		EvaluationContext context = expressionEvaluator.createContext(rootObject, true);
		AnnotatedElementKey elementKey = new AnnotatedElementKey(rootObject.getMethod(), rootObject.getTargetClass());
		return expressionEvaluator.evalAsText(param, elementKey, context);
	}

}
